# OSWorkflow核心概念

image-20240905124136968
  • 项目地址:https://github.com/ailohq/osworkflow

OSWorkflow(Open Symphony Workflow)是一个灵活的、可扩展的、基于Java的工作流引擎。它的工作流引擎是基于有限状态机(Finite State Machine,简称FSM)概念实现的。有限状态机是一种抽象的计算模型,用来表示和控制执行流程。在OSWorkflow中,有限状态机用于描述工作流的执行过程和状态转换。

在OSWorkflow中,每个工作流都被定义为一个有限状态机。每个状态机包含一系列的状态(State,即步骤step+状态status)、转换(Transition)和动作(Action)。状态表示工作流的当前位置,转换表示状态之间的流转,动作则是引导从一个状态到另一个状态的行为。

以下是OSWorkflow基于有限状态机的实现的基本步骤:

  1. 定义工作流:在OSWorkflow中,工作流是通过XML文件进行定义的。这个XML文件描述了工作流的所有状态、转换和动作。
  2. 创建工作流实例:当需要启动一个新的工作流时,OSWorkflow引擎会根据工作流定义创建一个新的工作流实例。
  3. 执行动作:当工作流实例在某个状态时,可以执行一个或多个动作。这些动作可能会改变工作流实例的状态,引导工作流实例向前流转。
  4. 状态转换:当执行动作后,工作流实例的状态可能会发生改变。这个改变就是一个状态转换。状态转换是由动作触发的。
  5. 条件判断:在执行动作和状态转换时,OSWorkflow引擎会进行条件判断。只有当满足特定条件时,动作才会被执行,状态才会发生转换。
  6. 结束工作流:当工作流实例达到终止状态时,工作流就结束了。

通过以上步骤,OSWorkflow引擎实现了基于有限状态机的工作流管理。这种方式使得工作流的流程和规则变得清晰明了,便于理解和管理。下面我们将进一步了解它的实现原理。

正常一个OSWorkflow工作流包含多个步骤。每一个步骤都有一个当前状态(例如,:Queued,、Underway,或 Finished)。每一个步骤中都有一个或者多个动作可以被执行。每一个动作都可以设置执行条件(condition),也可以设置执行函数(pre-function 或 post-function)。动作产生的结果(result)会导致工作流的状态和当前步骤发生改变。

工作流的xml定义

如下是一个OSWorkflow工作流定义的组成部份。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC "-//Open Source Workflow//DTD OSWf 3.0//EN" 
          "http://oswf.sourceforge.net/OSWf-3.0.dtd">
<workflow>

  <initial-actions>
        .
        .
        .
  </initial-actions>


  <steps>
     <step> 
      <actions> 
        <action> 
          <results> 
            <results> 
              <unconditional-result old-status="xxx" status="yy" step="zz"/>
              <conditional-result .../>
            </results> 
          </results> 
        </action> 
        ...
      </actions> 
    </step> 
     .
     .
  </steps>

</workflow>

首先是标准的XML头部,要注意的是OSWorkflow将会通过这些指定的DTD来验证XML内容的合法性。你可以使用绝大多数的XML编辑工具来编辑它。

  • workflow表示一个工作流

  • initial-actions定义初始化步骤。初始化步骤是一种特殊类型的步骤,它用来启动工作流。在一个工作流程开始前,它是没有状态的,不处在任何一个步骤,用户必须采取某些动作才能开始这个流程。这些特殊步骤被定义在 。例如下面的:

    这个动作只是简单的说明了下一个我们要去的步骤和状态。触发动作action发生后,工作流会流转到步骤1,并且状态会变为Queued。

    <initial-actions>
      <action id="1" name="Start Workflow">
        <results>
          <unconditional-result old-status="Finished" status="Queued" step="1"/>
        </results>
      </action>
    </initial-actions>
    

下面,我们进一步分析其他构成元素。

# step(步骤)

Step表示工作流所处的位置,一个 Step 里面可以有一个或多个 Action(即actions数组)。执行哪一个action则取决于用户、外部事件或自动触发器。而一个工作流由多个Step来表示流程。

从一个step触发可以流转到另一个Step,也可以在同一个step内部流转(因为一个step可以有多个状态status)。

<workflow>
  <steps>
    <step>
      <actions>
        <action></action>
        ...
      </actions>
    </step>
    <step></step>
    ...
  </steps>
</workflow>

# action(动作)

action表示动作,每个步骤都有一个或多个action。

action 触发发生在 step 内或 step 之间的流转,或者说是基于 State 的流转。action 和step 之间的关系是,step 说明“在哪里”,action 说明“去哪里”。 每一个动作至少有一个无条件结果(unconditional result)和零到多个条件结果(conditional result) 。例如下面的例子:

该步骤叫First Draft,它定义了2个动作,分别是:Start First DraftFinish First Draft。每个步骤都有一个默认的无条件结果unconditional-result

其中old-status属性是用来指明当前步骤完成以后的状态是什么,大部分情况下通常用"Finished"表示。

而status属性表示该流程实例的状态status,status的取值可以是Underway或者Queued。

由于下面定义的两个动作没有任何限制,所以用户可以任意调用一个动作都可以。

<step id="1" name="First Draft">
  <actions>
    <action id="1" name="Start First Draft">
      <results>
        <unconditional-result old-status="Finished" status="Underway" step="1"/>
      </results>
    </action>
    
    <action id="2" name="Finish First Draft">
      <results>
        <unconditional-result old-status="Finished" status="Queued" step="2"/>
      </results>
    </action>
  </actions>
</step>
<step id="2" name="finished" />

正常来说上面应该要先完成Start First Draft,才能开始Finish First Draft。但是上面因为没有做限制导致没有先后顺序。

要解决上面的问题,我们可以通过下面的condition条件做约束。

# condition(条件)

条件(Condition)是用于控制工作流转换(Transition)是否可以发生的关键组件。条件的结果为真(true)时,转换可以发生;结果为假(false)时,转换无法发生。每个动作可以设置0个或多个约束条件。

在OSWorkflow中内置了很多条件,例如下面我们会用到的com.opensymphony.workflow.util.StatusCondition条件。

com.opensymphony.workflow.util.StatusCondition用于检查工作流实例的状态。具体来说,它会比较工作流实例当前步骤(Step)的状态与预定义的状态值,以确定条件是否满足。如果当前步骤的状态与预定义的状态值相等,则条件结果为真(true),否则为假(false)。

要使用 StatusCondition,需要在工作流定义文件(例如 workflow.xml)中添加一个条件元素(condition element),并指定其类型为 com.opensymphony.workflow.util.StatusCondition。然后,需要配置一个参数(argument),用于指定预定义的状态值。例如下面我们对Start First Draft动作添加condition条件。

<action id="1" name="Start First Draft">
  <restrict-to>
    <conditions>
      <condition type="class">
        <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
        <arg name="status">Queued</arg>
      </condition>
    </conditions>
  </restrict-to>
  <results>
    <unconditional-result old-status="Finished" status="Underway" step="1"/>
  </results>
</action>

其中的<restrict-to> 标签定义了一组条件,只有当这些条件都满足时,这个动作才能被执行。上面的条件定义保证了action动作1只能在当前状态为“Queued”的时候才能被调用,也就是说在初始化动作被调用以后。

接下来,我们想在一个用户开始Start First Draft以后,设置他为“owner”。为了达到这样的目的,我们需要做2件事情:

  1. 通过一个函数设置caller变量在当前的环境设置里。
  2. 根据caller变量来设置owner属性。com.opensymphony.workflow.util.Caller是Osworkflow提供的内置函数,该函数获得当前调用工作流的用户,并放入一个名为caller的字符型变量中。
<action id="1" name="Start First Draft">
  <restrict-to>
    <conditions>
      <condition type="class">
        <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
        <arg name="status">Queued</arg>
      </condition>
    </conditions>
  </restrict-to>
  <pre-functions>
    <function type="class">
      <arg name="class.name">com.opensymphony.workflow.util.Caller</arg>
    </function>
  </pre-functions>
  <results>
    <unconditional-result old-status="Finished" status="Underway" step="1"  owner="${caller}"/>
  </results>
</action>

这段 XML 定义了一个动作,这个动作在工作流实例的当前步骤的状态为 "Queued" 时可以被执行,执行的结果是将工作流实例的状态改为 "Underway",并将工作流实例移动到步骤 1。

我们也对动作2设置条件,如下:

<action id="2" name="Finish First Draft">
  <restrict-to>
    <conditions type="AND">
      <condition type="class">
        <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
        <arg name="status">Underway</arg>
      </condition>
      <condition type="class">
        <arg name="class.name">com.opensymphony.workflow.util.AllowOwnerOnlyCondition</arg>
      </condition>
    </conditions>
  </restrict-to>
  <results>
    <unconditional-result old-status="Finished" status="Queued" step="2"/>
  </results>
</action>

在这段XML定义中,<restrict-to> 使用了一个类型为 "AND" 的条件组合,表示这两个条件都需要满足才能执行。

  • 第一个使用 StatusCondition 类来检查工作流实例的状态。具体来说,只有当工作流实例的当前步骤的状态为 "Underway" 时,这个动作才能被执行。
  • 第二个使用AllowOwnerOnlyCondition 类来检查当前用户是否为步骤的所有者。只有当当前用户是步骤的所有者时,这个动作才能被执行。

这段 XML 定义了一个动作,这个动作在工作流实例的当前步骤的状态为 "Underway" 且当前用户是步骤的所有者时可以被执行,执行的结果是将工作流实例的状态改为 "Queued",并将工作流实例移动到步骤 2。

# pre-function、post-function(前置函数、后置函数)

# action动作中的函数定义

在 OSWorkflow 中,每个动作(Action)都可以配置前置函数(Pre-function)和后置函数(Post-function)。这些函数在动作执行的前后被调用,用于执行一些特定的操作,例如修改工作流实例的属性、验证数据、发送通知等。

![image-20240318225137351](./img/4.2 osworkflow-action函数调用.png)

  1. 前置函数(Pre-function):这些函数在动作执行前被调用。它们通常用于准备动作的执行环境,例如设置动作的输入参数、验证数据的有效性、检查权限等。如果前置函数执行失败(例如数据验证失败),动作将不会被执行。
  2. 后置函数(Post-function):这些函数在动作执行后被调用。它们通常用于处理动作的执行结果,例如更新工作流实例的状态、保存数据、发送通知等。如果后置函数执行失败(例如数据保存失败),动作的执行结果可能会被回滚。

在 OSWorkflow 的 XML 定义中,前置函数和后置函数使用 <pre-functions><post-functions> 标签进行配置。每个函数都有一个类型(type)和一组参数(arg)。类型用于指定函数的实现类,参数用于配置函数的输入参数。例如:

<action id="1" name="Approve">
  <pre-functions>
    <function type="class">
      <arg name="class.name">com.example.workflow.MyPreFunction</arg>
    </function>
  </pre-functions>
  <post-functions>
    <function type="class">
      <arg name="class.name">com.example.workflow.MyPostFunction</arg>
    </function>
  </post-functions>
  ...
</action>

在这个例子中,动作 "Approve" 有一个前置函数 MyPreFunction 和一个后置函数 MyPostFunction。在动作执行前,MyPreFunction 将被调用;在动作执行后,MyPostFunction 将被调用。

# step步骤中的函数定义

如果是在一个 step 中定义的 pre-function 和 post-function,用法会有所不同,pre-function 将会在工作流流转到这个 step 之前执行。注意这个 pre-function 将会在任何目的是此步骤的流 转发生之前执行,甚至是这个步骤本身(举个例子,如果状态从 Queued 转换到 Finished 但并 未改变步骤也会执行 pre-function)。 同样的,step的post-functions 也将会在工作流传递出这个步骤之前调用,甚至当它自己改变状 态但步骤不发生改变也是如此。 下图的图解说明了调用顺序。注意 action 虚线内做了一些抽象,它自己也可以有 per-function 和 post-function。

![image-20240318224915637](./img/4.2 osworkflow种step的函数调用.png)

# result(结果)

result(结果)是用于描述工作流动作(action)执行后的输出。它定义了工作流实例(workflow instance)在动作执行完成后应该转移到的下一个步骤(step),以及与之相关的状态、所有者和其他属性的变更。result 在工作流定义文件(例如 workflow.xml)中的 <results> 标签内进行配置。

对于每一个动作,都要求至少存在一个结果,称为 Unconditional- Result(无条件结果)。结果只不过是一系列指令,告诉流程引擎下一步是什么。这涉及在构成给定工作流的状态机中从一个步骤到下一步的转换。

在 OSWorkflow 中,有两种类型的结果:

  • 无条件结果(Unconditional Result):这是最常见的结果类型,表示在动作执行后,无论条件是否满足,都会发生的结果。无条件结果使用 <unconditional-result> 标签进行定义。例如:
<unconditional-result old-status="InProgress" status="Completed" step="2"/>

在这个例子中,无条件结果将工作流实例的状态从 "InProgress" 更改为 "Completed",并将实例移动到步骤 2。

  • 条件结果(Conditional Result):这种结果类型表示只有在满足某些条件时才会发生的结果。条件结果使用 <conditional-result> 标签进行定义,并在其中配置一个或多个条件(condition)。例如:
<conditional-result>
  <conditions>
    <condition type="class">
      <arg name="class.name">com.opensymphony.workflow.util.StatusCondition</arg>
      <arg name="status">Approved</arg>
    </condition>
  </conditions>
  <result old-status="InProgress" status="Completed" step="2"/>
</conditional-result>

在这个例子中,条件结果会检查工作流实例的当前步骤状态是否为 "Approved"。如果满足该条件,结果将工作流实例的状态从 "InProgress" 更改为 "Completed",并将实例移动到步骤 2。

执行结果后的三种选择

在 OSWorkflow 中,执行完 result(结果)后,工作流实例(workflow instance)可能会转移到三种不同的情况:到达 step(步骤)、到达 split(分支)和到达 join(聚合)。下面分别介绍这三种情况:

  • 到达 step(步骤):这是最常见的情况,表示工作流实例在执行完动作(action)后,到达了一个新的步骤。步骤是工作流中的一个节点,用于表示工作流实例的状态和执行位置。在执行完 result 后,工作流实例会根据 result 的配置,将其状态、所有者等属性进行更改,并转移到下一个步骤。例如:

    <unconditional-result old-status="InProgress" status="Completed" step="2"/>
    

    在这个例子中,执行完 result 后,工作流实例的状态从 "InProgress" 更改为 "Completed",并转移到步骤 2。

  • 到达 split(分支):这种情况表示工作流实例在执行完动作后,到达了一个分支节点。分支节点用于在工作流中创建多个并行执行路径,从而实现复杂的业务逻辑。在到达分支节点后,工作流实例会根据分支节点的配置,同时进入多个步骤,并在这些步骤中并行执行。例如:

    <unconditional-result old-status="InProgress" status="Completed" split="2"/>
    ...
    <splits>
      <split id="2">
        <unconditional-result old-status="Finished" step="2" status="Underway" />
        <unconditional-result old-status="Finished" step="3" status="Underway" />
      </split>
    </splits>
    

    在这个例子中,执行完 result 后,工作流实例的状态从 "InProgress" 更改为 "Completed",并转移到分支节点 2。

  • 到达 join(聚合):这种情况表示工作流实例在执行完动作后,到达了一个聚合节点。聚合节点用于将多个并行执行路径重新合并为一个路径,从而实现对工作流实例的同步。在到达聚合节点后,工作流实例会等待所有并行执行路径都完成,然后根据聚合节点的配置,将其状态、所有者等属性进行更改,并转移到下一个步骤。例如:

    <!-- for step id 6 ->
    <unconditional-result join="1"/>
    ...
    <!- for step id 8 ->
    <unconditional-result join="1"/>
    ...
    <joins>
      <join id="1">
        <conditions type="AND">
          <condition type="beanshell">
            <arg name="script">
              "Finished".equals(jn.getStep(6).getStatus()
              && "Finished".equals(jn.getStep(8).getStatus())
            </arg>
          </condition>
        </conditions>
        <unconditional-result old-status="Finished" status="Underway" step="2"/>
      </join>
    </joins>
    

    我们定义了一个 ID 为 1 的聚合节点。这个聚合节点有一个条件,使用 BeanShell 脚本来检查步骤 6 和步骤 8 的状态是否都为 "Finished"。只有当这两个步骤的状态都为 "Finished" 时,这个聚合节点才会被执行。

    执行聚合节点后,有一个无条件结果,将工作流实例的状态更改为 "Underway",并将实例移动到步骤 2。

# OSWorkflow FSM核心方法剖析

# 工作流案例

下面,我们通过创建一个简单的工作流,追踪它执行动作的代码过程来进行分析。

首先应该载入流程定义文件创建定义工作流的文件(在 XML 里面)。

在开始载入流程定义、调用动作以前,我们需要配置OSWorkflow的数据存储方式和定义文件的位置等。

osworkflow.xml

下面指明了我们准备使用内存 (MemoryWorkflowStore) 来保存流程数据。这样可以减少设置数据库的相关信息,减少出问题的可能性。用内存持久化对于测试来说是非常方便的。

<osworkflow>
	<persistence
		class="com.opensymphony.workflow.spi.memory.MemoryWorkflowStore" />
	<factory
		class="com.opensymphony.workflow.loader.XMLWorkflowFactory">
		<property key="resource" value="workflows.xml" />
	</factory>
</osworkflow>

上面的配置文件还指明了我们工作流工厂(XMLWorkflowFactory),工作流工厂的主要功能是管理流程定义文件,包括读取定义文件和修改定义文件的功能。通过resource这个属性指明了采用通过从classpath中读取流程定义文件的方式,按照这个定义,接下来我们需要在classpath中创建一个名为workflows.xml的文件。

workflows.xml

<workflows>
	<workflow name="mytest" type="resource" location="myworkflow.xml" />
</workflows>

myworkflow.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE workflow PUBLIC 
  "-//OpenSymphony Group//DTD OSWorkflow 2.7//EN"
  "http://www.opensymphony.com/osworkflow/workflow_2_7.dtd"> 
<workflow>
  <initial-actions>
    <action id="1" name="Start Workflow">
      <results>
        <unconditional-result old-status="Finished"
        status="Queued" step="1"/>
      </results>
    </action>
  </initial-actions>
  <steps>
    <step id="1" name="First Draft">
      <actions>
        <action id="1" name="Start First Draft">
          <restrict-to>
            <conditions>
              <condition type="class">
                <arg name="class.name">
                   com.opensymphony.workflow.util.StatusCondition
                </arg>
                <arg name="status">Queued</arg>
              </condition>
            </conditions>
          </restrict-to>
          <pre-functions>
            <function type="class">
              <arg name="class.name">
                 com.opensymphony.workflow.util.Caller
              </arg>
            </function>
          </pre-functions>
          <results>
            <unconditional-result old-status="Finished" status="Underway" 
            step="1"  owner="${caller}"/>
          </results>
        </action>
        <action id="2" name="Finish First Draft">
          <restrict-to>
            <conditions type="AND">
              <condition type="class">
                <arg name="class.name">
                    com.opensymphony.workflow.util.StatusCondition
                </arg>
                <arg name="status">Underway</arg>
              </condition>
              <condition type="class">
                <arg name="class.name">
                  com.opensymphony.workflow.util.AllowOwnerOnlyCondition
                </arg>
              </condition>
            </conditions>
          </restrict-to>
          <results>
            <unconditional-result old-status="Finished"
            status="Queued" step="2"/>
          </results>
        </action>
      </actions>
    </step>
    <step id="2" name="finished" />
  </steps>
</workflow>

# 工作流测试

接下来,我们从测试工作流开始切入其源码分析。

OSWorkflow关于状态机的核心代码都在AbstractWorkflow中,其中EJBWorkflow 、 SOAPWorkflow、BasicWorkflow扩展自它。AbstractWorkflow实现了Workflow接口,该接口包含了工作流的核心方法,其中最重要的就是doAction方法。 为了简单起见,我们使用最基本的一种: BasicWorkflow。

String caller = "tester";
long workflowId = 1;

// 下一步是提供配置文件,在大多数情况下,只是简单的传递一个DefaultConfiguration实例:
// 现在我们已经创建并且配置好了一个workflow,接下来就是开始调用它了。
DefaultConfiguration config = new DefaultConfiguration();
workflow.setConfiguration(config);

// 首先我们需要调用initialize 方法来启动一个工作流程,这个方法有3个参数,workflow name (定义在workflows.xml里,通过workflow factory处理), action ID (我们要调用的初始化动作的ID,这里是1),和初始化变量。 因为在例子里面不需初始化变量,所以我们只是传递一个null,
Workflow workflow = new BasicWorkflow(caller);
workflowId = workflow.initialize("mytest", 1, null);

// 这是简单的调用第一个动作,工作流引擎根据指定的条件,改变状态到‘Underway’,并且保持在当前步骤。
workflow.doAction(workflowId, 1, null);

# 核心方法分析

根据前面工作流测试的代码,我们通过去分析其中几个核心方法的源码来了解OSWorkflow实现FSM的原理:

# initialize方法

initialize方法是用来初始化一个工作流的。函数接收三个参数:一个字符串类型的workflowName,一个整数类型的initialAction和一个Map类型的inputs。

initialize初始化方法主要完成了以下几个功能:

  • 首先,它从配置中获取工作流描述符WorkflowDescriptor对象wf,然后从持久化存储中获取WorkflowStore对象store,并创建一个新的工作流条目WorkflowEntry对象entry。
  • 然后,将工作流条目的ID和名称放入inputs map中。创建一个新的属性集合PropertySet对象ps,它是从持久化存储中获取的,然后创建一个新的Map对象transientVars。
  • 如果inputs不为空,将其所有元素都添加到transientVars中。然后,使用populateTransientMap方法填充transientVars。
  • 接着,如果不能初始化工作流,则回滚上下文并抛出InvalidRoleException异常。
  • 然后,获取初始动作描述符ActionDescriptor对象action,然后尝试转换工作流。如果在转换过程中发生WorkflowException异常,则回滚上下文并抛出该异常。
  • 最后,获取工作流条目的ID,并返回该ID。

以下是initialize方法的完整代码:

路径:https://github.com/ailohq/osworkflow/blob/master/src/main/java/com/opensymphony/workflow/AbstractWorkflow.java#L607

public long initialize(String workflowName, int initialAction, Map inputs) throws InvalidRoleException, InvalidInputException, WorkflowException {
        WorkflowDescriptor wf = getConfiguration().getWorkflow(workflowName);

        WorkflowStore store = getPersistence();
        WorkflowEntry entry = store.createEntry(workflowName);

        // Chanthu
        inputs.put(WORKFLOW_ID_KEY, Long.toString(entry.getId()));
        inputs.put(WORKFLOW_NAME_KEY, entry.getWorkflowName());

        // start with a memory property set, but clone it after we have an ID
        PropertySet ps = store.getPropertySet(entry.getId());
        Map transientVars = new HashMap();

        if (inputs != null) {
            transientVars.putAll(inputs);
        }

        populateTransientMap(entry, transientVars, wf.getRegisters(), new Integer(initialAction), Collections.EMPTY_LIST, ps);

        if (!canInitialize(workflowName, initialAction, transientVars, ps)) {
            context.setRollbackOnly();
            throw new InvalidRoleException("You are restricted from initializing this workflow");
        }

        ActionDescriptor action = wf.getInitialAction(initialAction);

        try {
            transitionWorkflow(entry, Collections.EMPTY_LIST, store, wf, action, transientVars, inputs, ps);
        } catch (WorkflowException e) {
            context.setRollbackOnly();
            throw e;
        }

        long entryId = entry.getId();

        // now clone the memory PS to the real PS
        // PropertySetManager.clone(ps, store.getPropertySet(entryId));
        return entryId;
    }

前面的初始化代码,我们看到获取了初始动作后,开始调用transitionWorkflow方法进行状态转换,接下来我们对这个方法进行分析。

# transitionWorkflow方法

transitionWorkflow方法

transitionWorkflow函数主要目的是处理工作流实例的状态转换。以下是这个函数的主要功能:

  1. 清除或初始化状态缓存。

    Map cache = (Map) stateCache.get();
    if (cache != null) {
        cache.clear();
    } else {
        stateCache.set(new HashMap());
    }
    
  2. 获取当前步骤。

    Step step = getCurrentStep(wf, action.getId(), currentSteps, transientVars, ps);	
    
  3. 验证输入。

    if (action.getValidators().size() > 0) {
        verifyInputs(entry, action.getValidators(), Collections.unmodifiableMap(transientVars), ps);
    }
    

    如果动作有验证器,使用verifyInputs方法验证输入参数。

  4. 执行步骤的后置函数。

    if (step != null) {
        List stepPostFunctions = wf.getStep(step.getStepId()).getPostFunctions();
        for (Iterator iterator = stepPostFunctions.iterator(); iterator.hasNext(); ) {
            FunctionDescriptor function = (FunctionDescriptor) iterator.next();
            executeFunction(function, transientVars, ps);
        }
    }
    

    如果当前步骤不为空,执行步骤的后置函数。

  5. 执行动作的前置函数。

    List preFunctions = action.getPreFunctions();
    for (Iterator iterator = preFunctions.iterator(); iterator.hasNext(); ) {
        FunctionDescriptor function = (FunctionDescriptor) iterator.next();
        executeFunction(function, transientVars, ps);
    }
    

    遍历动作的前置函数列表,执行每个前置函数。

  6. 处理条件结果

    List conditionalResults = action.getConditionalResults();
    List extraPreFunctions = null;
    List extraPostFunctions = null;
    ResultDescriptor[] theResults = new ResultDescriptor[1];
    ....
    

    这部分代码首先初始化条件结果、额外的前置函数和后置函数列表。然后遍历条件结果,检查是否满足条件。如果满足条件,执行额外的前置函数和后置函数。

  7. 处理分支(split)和连接(join)操作,见下面代码片段。

    if (theResults[0].getSplit() != 0) {
        ...
    } else if (theResults[0].getJoin() != 0) {
        ...
    } else {
        ...
    }
    

    7.1 处理分支(split)

    处理分支(Split)的代码用于处理工作流中的分支逻辑。分支意味着工作流会根据条件分解为多个并行的子流程。

    这里结合前面的spli的xml定义一起分析会更清晰。theResults是当前动作的结果,theResults[0]表示取第一个split元素。每个split里面有多个无条件结果,每个无条件结果都会指向新的步骤和状态。

    <unconditional-result old-status="InProgress" status="Completed" split="2"/>
    ...
    <splits>
      <split id="2">
        <unconditional-result old-status="Finished" step="2" status="Underway" />
        <unconditional-result old-status="Finished" step="3" status="Underway" />
      </split>
    </splits>
    

    以下是对这部分代码的执行功能:

    • 7.1.1 获取分支描述符和结果:

      SplitDescriptor splitDesc = wf.getSplit(theResults[0].getSplit());
      Collection results = splitDesc.getResults();
      

      这部分代码首先获取分支描述符,然后从分支描述符中获取结果集合。

    • 7.1.2 验证输入和收集函数:

      for (Iterator iterator = results.iterator(); iterator.hasNext(); ) {
          ResultDescriptor resultDescriptor = (ResultDescriptor) iterator.next();
          if (resultDescriptor.getValidators().size() > 0) {
              verifyInputs(entry, resultDescriptor.getValidators(), Collections.unmodifiableMap(transientVars), ps);
          }
          splitPreFunctions.addAll(resultDescriptor.getPreFunctions());
          splitPostFunctions.addAll(resultDescriptor.getPostFunctions());
      }
      

      这部分代码遍历结果集合,对每个结果进行输入验证,并收集所有的前置函数和后置函数。

    • 7.1.3 执行前置函数:

      for (Iterator iterator = splitPreFunctions.iterator(); iterator.hasNext(); ) {
          FunctionDescriptor function = (FunctionDescriptor) iterator.next();
          executeFunction(function, transientVars, ps);
      }
      

      这部分代码遍历前置函数列表,执行每个前置函数。

    • 7.1.4 创建新的当前步骤:

      if (!action.isFinish()) {
          boolean moveFirst = true;
          theResults = new ResultDescriptor[results.size()];
          results.toArray(theResults);
          for (Iterator iterator = results.iterator(); iterator.hasNext(); ) {
              ResultDescriptor resultDescriptor = (ResultDescriptor) iterator.next();
              Step moveToHistoryStep = null;
              if (moveFirst) {
                  moveToHistoryStep = step;
              }
              long[] previousIds = null;
              if (step != null) {
                  previousIds = new long[]{step.getId()};
              }
              createNewCurrentStep(resultDescriptor, entry, store, action.getId(), moveToHistoryStep, previousIds, transientVars, ps);
              moveFirst = false;
          }
      }
      

      如果当前动作不是结束动作,这部分代码会为每个分支结果创建一个新的当前步骤。createNewCurrentStep方法用于创建新的当前步骤,并将旧的当前步骤移动到历史步骤中。

    • 7.1.5 执行后置函数:

      for (Iterator iterator = splitPostFunctions.iterator(); iterator.hasNext(); ) {
          FunctionDescriptor function = (FunctionDescriptor) iterator.next();
          executeFunction(function, transientVars, ps);
      }
      

      这部分代码遍历后置函数列表,执行每个后置函数。

    7.2 处理连接(join)

    结合join的xml定义一起分析下面的代码:

    <joins>
      <join id="1">
        <conditions type="AND">
          <condition type="beanshell">
            <arg name="script">
              "Finished".equals(jn.getStep(6).getStatus()
              && "Finished".equals(jn.getStep(8).getStatus())
            </arg>
          </condition>
        </conditions>
        <unconditional-result old-status="Finished" status="Underway" step="2"/>
      </join>
    </joins>
    
    • 7.2.1 获取连接描述符:

      JoinDescriptor joinDesc = wf.getJoin(theResults[0].getJoin());	
      
    • 7.2.2 标记当前步骤为已完成并移至历史步骤:

      step = store.markFinished(step, action.getId(), new Date(), theResults[0].getOldStatus(), context.getCaller());
      store.moveToHistory(step);
      

      这部分代码将当前步骤标记为已完成,并将其移至历史步骤中。

    • 7.2.3 收集参与连接的步骤:

      Collection joinSteps = new ArrayList();
      joinSteps.add(step);
      for (Iterator iterator = currentSteps.iterator(); iterator.hasNext(); ) {
          Step currentStep = (Step) iterator.next();
          if (currentStep.getId() != step.getId()) {
              StepDescriptor stepDesc = wf.getStep(currentStep.getStepId());
              if (stepDesc.resultsInJoin(theResults[0].getJoin())) {
                  joinSteps.add(currentStep);
              }
          }
      }
      

      这部分代码首先将当前步骤添加到参与连接的步骤集合中,然后遍历当前的步骤,如果步骤的描述符表示它会结果在当前的连接,那么就将它添加到参与连接的步骤集合中。

    • 7.2.4 检查历史步骤:

      List historySteps = store.findHistorySteps(entry.getId());
      for (Iterator i = historySteps.iterator(); i.hasNext(); ) {
          Step historyStep = (Step) i.next();
          if (historyStep.getId() != step.getId()) {
              StepDescriptor stepDesc = wf.getStep(historyStep.getStepId());
              if (stepDesc.resultsInJoin(theResults[0].getJoin())) {
                  joinSteps.add(historyStep);
              }
          }
      }
      
    • 7.2.5 检查连接条件:

      JoinNodes jn = new JoinNodes(joinSteps);
      transientVars.put("jn", jn);
      if (passesConditions(null, joinDesc.getConditions(), Collections.unmodifiableMap(transientVars), ps, 0)) {
          ...
      }
      

      这部分代码首先创建一个JoinNodes对象,然后检查是否满足连接的条件。如果满足条件,就执行后续的代码。

    • 7.2.6 验证输入并执行前置函数:

      ResultDescriptor joinresult = joinDesc.getResult();
      if (joinresult.getValidators().size() > 0) {
          verifyInputs(entry, joinresult.getValidators(), Collections.unmodifiableMap(transientVars), ps);
      }
      for (Iterator iterator = joinresult.getPreFunctions().iterator(); iterator.hasNext(); ) {
          FunctionDescriptor function = (FunctionDescriptor) iterator.next();
          executeFunction(function, transientVars, ps);
      }
      

      这部分代码首先获取连接的结果,然后验证输入参数,最后执行前置函数。

    • 7.2.7 创建新的当前步骤:

      long[] previousIds = new long[joinSteps.size()];
      int i = 1;
      for (Iterator iterator = joinSteps.iterator(); iterator.hasNext(); ) {
          Step currentStep = (Step) iterator.next();
          if (currentStep.getId() != step.getId()) {
              if (!historySteps.contains(currentStep)) {
                  store.moveToHistory(currentStep);
              }
              previousIds[i] = currentStep.getId();
              i++;
          }
      }
      if (!action.isFinish()) {
          previousIds[0] = step.getId();
          theResults[0] = joinDesc.getResult();
          createNewCurrentStep(joinDesc.getResult(), entry, store, action.getId(), null, previousIds, transientVars, ps);
      }
      

      这部分代码首先创建一个数组用于保存参与连接的步骤的ID,然后遍历参与连接的步骤,将步骤移至历史步骤并保存其ID。然后,如果当前动作不是结束动作,就调用createNewCurrentStep方法创建新的当前步骤。

    • 7.2.8 执行后置函数:

      for (Iterator iterator = joinresult.getPostFunctions().iterator(); iterator.hasNext(); ) {
          FunctionDescriptor function = (FunctionDescriptor) iterator.next();
          executeFunction(function, transientVars, ps);
      }
      

      这部分代码遍历后置函数列表,执行每个后置函数。

  8. 如果程序进入到另一个步骤step(不是split和join),结束当前步骤并将其移至历史步骤中去。若动作未结束,则创建新的步 骤并执行新步骤中的 pre-function前置函数。

    long[] previousIds = null;
    
    if (step != null) {
        previousIds = new long[]{step.getId()};
    }
    if (!action.isFinish()) {
        createNewCurrentStep(theResults[0], entry, store, action.getId(), step, previousIds, transientVars, ps);
    }
    
  9. 执行动作的后置函数

    List postFunctions = action.getPostFunctions();
    for (Iterator iterator = postFunctions.iterator(); iterator.hasNext(); ) {
        FunctionDescriptor function = (FunctionDescriptor) iterator.next();
        executeFunction(function, transientVars, ps);
    }
    

    遍历动作的后置函数列表,执行每个后置函数。

  10. 更新工作流实例状态

    if ((wf.getInitialAction(action.getId()) != null) && (entry.getState() != WorkflowEntry.ACTIVATED)) {
        changeEntryState(entry.getId(), WorkflowEntry.ACTIVATED);
    }
    

    如果执行的动作是初始动作且工作流实例的状态不是已激活(ACTIVATED),则更新工作流实例的状态为已激活。

  11. 检查是否完成工作流实例

    if (action.isFinish()) {
        completeEntry(action, entry.getId(), getCurrentSteps(entry.getId()), WorkflowEntry.COMPLETED);
        return true;
    }
    

    如果执行的动作是结束动作,调用completeEntry方法完成工作流实例,并返回true

  12. 执行自动执行的动作

    if (availableAutoActions.length > 0) {
        doAction(entry.getId(), availableAutoActions[0], inputs);
    }
    

    这部分代码首先获取可用的自动执行动作,然后执行第一个自动执行动作。在OSWorkflow中,自动执行动作是指在当前动作完成后,系统会自动执行的后续动作。

  13. 返回false表示工作流实例尚未完成:

    return false;
    

​ 如果代码执行到这里,说明工作流实例尚未完成,返回false

下面是完整的函数代码:

路径:https://github.com/ailohq/osworkflow/blob/master/src/main/java/com/opensymphony/workflow/AbstractWorkflow.java#L1025

/**
     * @return true if the instance has been explicitly completed is this
     * transition, false otherwise
     * @throws WorkflowException
     */
    protected boolean transitionWorkflow(WorkflowEntry entry, List currentSteps, WorkflowStore store, WorkflowDescriptor wf, ActionDescriptor action, Map transientVars, Map inputs, PropertySet ps)
            throws WorkflowException {
        // 清除或初始化状态缓存
        Map cache = (Map) stateCache.get();

        if (cache != null) {
            cache.clear();
        } else {
            stateCache.set(new HashMap());
        }

        // 获取当前步骤
        Step step = getCurrentStep(wf, action.getId(), currentSteps, transientVars, ps);
  
        // 如果动作有验证器,使用verifyInputs方法验证输入参数
        if (action.getValidators().size() > 0) {
            verifyInputs(entry, action.getValidators(), Collections.unmodifiableMap(transientVars), ps);
        }

        // 执行步骤的后置函数
        if (step != null) {
            List stepPostFunctions = wf.getStep(step.getStepId()).getPostFunctions();

            for (Iterator iterator = stepPostFunctions.iterator(); iterator.hasNext(); ) {
                FunctionDescriptor function = (FunctionDescriptor) iterator.next();
                executeFunction(function, transientVars, ps);
            }
        }

        // 执行动作的前置函数
        List preFunctions = action.getPreFunctions();

        for (Iterator iterator = preFunctions.iterator(); iterator.hasNext(); ) {
            FunctionDescriptor function = (FunctionDescriptor) iterator.next();
            executeFunction(function, transientVars, ps);
        }

        // 这部分代码首先初始化条件结果、额外的前置函数和后置函数列表。然后遍历条件结果,检查是否满足条件。如果满足条件,执行额外的前置函数和后置函数
        List conditionalResults = action.getConditionalResults();
        List extraPreFunctions = null;
        List extraPostFunctions = null;
        ResultDescriptor[] theResults = new ResultDescriptor[1];

        for (Iterator iterator = conditionalResults.iterator(); iterator.hasNext(); ) {
            ConditionalResultDescriptor conditionalResult = (ConditionalResultDescriptor) iterator.next();

            if (passesConditions(null, conditionalResult.getConditions(), Collections.unmodifiableMap(transientVars), ps, (step != null) ? step.getStepId() : (-1))) {
                // if (evaluateExpression(conditionalResult.getCondition(),
                // entry, wf.getRegisters(), null, transientVars)) {
                theResults[0] = conditionalResult;

                if (conditionalResult.getValidators().size() > 0) {
                    verifyInputs(entry, conditionalResult.getValidators(), Collections.unmodifiableMap(transientVars), ps);
                }

                extraPreFunctions = conditionalResult.getPreFunctions();
                extraPostFunctions = conditionalResult.getPostFunctions();

                break;
            }
        }

        // use unconditional-result if a condition hasn't been met
        if (theResults[0] == null) {
            theResults[0] = action.getUnconditionalResult();
            verifyInputs(entry, theResults[0].getValidators(), Collections.unmodifiableMap(transientVars), ps);
            extraPreFunctions = theResults[0].getPreFunctions();
            extraPostFunctions = theResults[0].getPostFunctions();
        }

        if (log.isDebugEnabled()) {
            log.debug("theResult=" + theResults[0].getStep() + ' ' + theResults[0].getStatus());
        }

        if ((extraPreFunctions != null) && (extraPreFunctions.size() > 0)) {
            // run any extra pre-functions that haven't been run already
            for (Iterator iterator = extraPreFunctions.iterator(); iterator.hasNext(); ) {
                FunctionDescriptor function = (FunctionDescriptor) iterator.next();
                executeFunction(function, transientVars, ps);
            }
        }

        // go to next step
        if (theResults[0].getSplit() != 0) {
            // 处理分支(Split)
            SplitDescriptor splitDesc = wf.getSplit(theResults[0].getSplit());
            Collection results = splitDesc.getResults();
            List splitPreFunctions = new ArrayList();
            List splitPostFunctions = new ArrayList();

            // check all results in the split and verify the input against any
            // validators specified
            // also build up all the pre and post functions that should be
            // called.
            for (Iterator iterator = results.iterator(); iterator.hasNext(); ) {
                ResultDescriptor resultDescriptor = (ResultDescriptor) iterator.next();

                if (resultDescriptor.getValidators().size() > 0) {
                    verifyInputs(entry, resultDescriptor.getValidators(), Collections.unmodifiableMap(transientVars), ps);
                }

                splitPreFunctions.addAll(resultDescriptor.getPreFunctions());
                splitPostFunctions.addAll(resultDescriptor.getPostFunctions());
            }

            // now execute the pre-functions
            for (Iterator iterator = splitPreFunctions.iterator(); iterator.hasNext(); ) {
                FunctionDescriptor function = (FunctionDescriptor) iterator.next();
                executeFunction(function, transientVars, ps);
            }

            if (!action.isFinish()) {
                // now make these steps...
                boolean moveFirst = true;

                theResults = new ResultDescriptor[results.size()];
                results.toArray(theResults);

                for (Iterator iterator = results.iterator(); iterator.hasNext(); ) {
                    ResultDescriptor resultDescriptor = (ResultDescriptor) iterator.next();
                    Step moveToHistoryStep = null;

                    if (moveFirst) {
                        moveToHistoryStep = step;
                    }

                    long[] previousIds = null;

                    if (step != null) {
                        previousIds = new long[]{step.getId()};
                    }

                    createNewCurrentStep(resultDescriptor, entry, store, action.getId(), moveToHistoryStep, previousIds, transientVars, ps);
                    moveFirst = false;
                }
            }

            // now execute the post-functions
            for (Iterator iterator = splitPostFunctions.iterator(); iterator.hasNext(); ) {
                FunctionDescriptor function = (FunctionDescriptor) iterator.next();
                executeFunction(function, transientVars, ps);
            }
        } else if (theResults[0].getJoin() != 0) {
            // 处理连接(Join)
            JoinDescriptor joinDesc = wf.getJoin(theResults[0].getJoin());
            step = store.markFinished(step, action.getId(), new Date(), theResults[0].getOldStatus(), context.getCaller());
            store.moveToHistory(step);

            // ... now check to see if the expression evaluates
            // (get only current steps that have a result to this join)
            Collection joinSteps = new ArrayList();
            joinSteps.add(step);

            // currentSteps = store.findCurrentSteps(id); // shouldn't need to
            // refresh the list
            for (Iterator iterator = currentSteps.iterator(); iterator.hasNext(); ) {
                Step currentStep = (Step) iterator.next();

                if (currentStep.getId() != step.getId()) {
                    StepDescriptor stepDesc = wf.getStep(currentStep.getStepId());

                    if (stepDesc.resultsInJoin(theResults[0].getJoin())) {
                        joinSteps.add(currentStep);
                    }
                }
            }

            // we also need to check history steps that were finished before
            // this one
            // that might be part of the join
            List historySteps = store.findHistorySteps(entry.getId());

            for (Iterator i = historySteps.iterator(); i.hasNext(); ) {
                Step historyStep = (Step) i.next();

                if (historyStep.getId() != step.getId()) {
                    StepDescriptor stepDesc = wf.getStep(historyStep.getStepId());

                    if (stepDesc.resultsInJoin(theResults[0].getJoin())) {
                        joinSteps.add(historyStep);
                    }
                }
            }

            JoinNodes jn = new JoinNodes(joinSteps);
            transientVars.put("jn", jn);

            // todo verify that 0 is the right value for currentstep here
            if (passesConditions(null, joinDesc.getConditions(), Collections.unmodifiableMap(transientVars), ps, 0)) {
                // move the rest without creating a new step ...
                ResultDescriptor joinresult = joinDesc.getResult();

                if (joinresult.getValidators().size() > 0) {
                    verifyInputs(entry, joinresult.getValidators(), Collections.unmodifiableMap(transientVars), ps);
                }

                // now execute the pre-functions
                for (Iterator iterator = joinresult.getPreFunctions().iterator(); iterator.hasNext(); ) {
                    FunctionDescriptor function = (FunctionDescriptor) iterator.next();
                    executeFunction(function, transientVars, ps);
                }

                long[] previousIds = new long[joinSteps.size()];
                int i = 1;

                for (Iterator iterator = joinSteps.iterator(); iterator.hasNext(); ) {
                    Step currentStep = (Step) iterator.next();

                    if (currentStep.getId() != step.getId()) {
                        // if this is already a history step (eg, for all join
                        // steps completed prior to this one),
                        // we don't move it, since it's already history.
                        if (!historySteps.contains(currentStep)) {
                            store.moveToHistory(currentStep);
                        }

                        previousIds[i] = currentStep.getId();
                        i++;
                    }
                }

                if (!action.isFinish()) {
                    // ... now finish this step normally
                    previousIds[0] = step.getId();
                    theResults[0] = joinDesc.getResult();

                    // we pass in null for the current step since we've already
                    // moved it to history above
                    createNewCurrentStep(joinDesc.getResult(), entry, store, action.getId(), null, previousIds, transientVars, ps);
                }

                // now execute the post-functions
                for (Iterator iterator = joinresult.getPostFunctions().iterator(); iterator.hasNext(); ) {
                    FunctionDescriptor function = (FunctionDescriptor) iterator.next();
                    executeFunction(function, transientVars, ps);
                }
            }
        } else {
            // normal finish, no splits or joins
            long[] previousIds = null;

            if (step != null) {
                previousIds = new long[]{step.getId()};
            }

            if (!action.isFinish()) {
                createNewCurrentStep(theResults[0], entry, store, action.getId(), step, previousIds, transientVars, ps);
            }
        }

        // postFunctions (BOTH)
        if (extraPostFunctions != null) {
            for (Iterator iterator = extraPostFunctions.iterator(); iterator.hasNext(); ) {
                FunctionDescriptor function = (FunctionDescriptor) iterator.next();
                executeFunction(function, transientVars, ps);
            }
        }

        // 执行后置函
        List postFunctions = action.getPostFunctions();

        for (Iterator iterator = postFunctions.iterator(); iterator.hasNext(); ) {
            FunctionDescriptor function = (FunctionDescriptor) iterator.next();
            executeFunction(function, transientVars, ps);
        }

        // if executed action was an initial action then workflow is activated
        // 如果执行的动作是初始动作且工作流实例的状态不是已激活(ACTIVATED),则更新工作流实例的状态为已激活
        if ((wf.getInitialAction(action.getId()) != null) && (entry.getState() != WorkflowEntry.ACTIVATED)) {
            changeEntryState(entry.getId(), WorkflowEntry.ACTIVATED);
        }

        // 如果执行的动作是结束动作,调用completeEntry方法完成工作流实例,并返回true
        if (action.isFinish()) {
            completeEntry(action, entry.getId(), getCurrentSteps(entry.getId()), WorkflowEntry.COMPLETED);

            return true;
        }

        // 执行自动执行的动作(调用上一步的doAction方法)
        int[] availableAutoActions = getAvailableAutoActions(entry.getId(), inputs);

        if (availableAutoActions.length > 0) {
            doAction(entry.getId(), availableAutoActions[0], inputs);
        }

        return false;
    }

# doAction方法

doAction函数的主要目的是执行一个工作流中的某个动作。doAction方法负责处理状态转换、执行相关的动作(Action)、调用条件判断(Conditions)以及触发后续步骤(Steps)等。

以下是对该函数的分段解释:

  1. 获取持久化的工作流存储对象,并根据传入的id查找对应的工作流实体(WorkflowEntry)。
WorkflowStore store = getPersistence();
WorkflowEntry entry = store.findEntry(id);
  1. 将工作流实体的ID和名称放入输入映射中。
inputs.put(WORKFLOW_ID_KEY, Long.toString(entry.getId()));
inputs.put(WORKFLOW_NAME_KEY, entry.getWorkflowName());
  1. 判断工作流实体的状态是否为激活状态,如果不是,则直接返回。
if (entry.getState() != WorkflowEntry.ACTIVATED) {
    return;
}
  1. 获取工作流描述符对象(WorkflowDescriptor),并查找当前步骤列表。
WorkflowDescriptor wf = getConfiguration().getWorkflow(entry.getWorkflowName());
List currentSteps = store.findCurrentSteps(id);
  1. 初始化动作描述符(ActionDescriptor)对象、属性集(PropertySet)对象和临时变量映射。
ActionDescriptor action = null;
PropertySet ps = store.getPropertySet(id);
Map transientVars = new HashMap();
  1. 将输入映射中的内容添加到临时变量映射中,并调用populateTransientMap方法填充临时变量映射。
if (inputs != null) {
    transientVars.putAll(inputs);
}
populateTransientMap(entry, transientVars, wf.getRegisters(), new Integer(actionId), currentSteps, ps);
  1. 遍历全局动作列表,查找与传入动作ID匹配的动作描述符,并检查该动作是否可用。如果可用,则将validAction设置为true。
boolean validAction = false;
for (Iterator gIter = wf.getGlobalActions().iterator(); !validAction && gIter.hasNext(); ) {
    ActionDescriptor actionDesc = (ActionDescriptor) gIter.next();
    if (actionDesc.getId() == actionId) {
        action = actionDesc;
        if (isActionAvailable(action, transientVars, ps, 0)) {
            validAction = true;
        }
    }
}
  1. 如果在全局动作列表中未找到有效动作,则遍历当前步骤列表,并在每个步骤的动作列表中查找与传入动作ID匹配的动作描述符。同样地,检查该动作是否可用,如果可用,则将validAction设置为true。
for (Iterator iter = currentSteps.iterator(); !validAction && iter.hasNext(); ) {
    Step step = (Step) iter.next();
    StepDescriptor s = wf.getStep(step.getStepId());
    for (Iterator iterator = s.getActions().iterator(); !validAction && iterator.hasNext(); ) {
        ActionDescriptor actionDesc = (ActionDescriptor) iterator.next();
        if (actionDesc.getId() == actionId) {
            action = actionDesc;
            if (isActionAvailable(action, transientVars, ps, s.getId())) {
                validAction = true;
            }
        }
    }
}
  1. 如果未找到有效动作,则抛出InvalidActionException异常。
if (!validAction) {
    throw new InvalidActionException("Action " + actionId + " is invalid");
}
  1. 调用transitionWorkflow方法尝试执行工作流转换。如果工作流未显式完成,则调用checkImplicitFinish方法检查是否可以隐式完成。
try {
    if (!transitionWorkflow(entry, currentSteps, store, wf, action, transientVars, inputs, ps)) {
        checkImplicitFinish(action, id);
    }
} catch (WorkflowException e) {
    context.setRollbackOnly();
    throw e;
}

整个函数的执行流程是:首先获取工作流实体和描述符,然后遍历全局动作和当前步骤的动作,查找有效动作。找到有效动作后,执行工作流转换,最后检查工作流是否完成。

下面是函数完整代码:

路径:https://github.com/ailohq/osworkflow/blob/master/src/main/java/com/opensymphony/workflow/AbstractWorkflow.java#L515

    public void doAction(long id, int actionId, Map inputs) throws WorkflowException {
        // 获取工作流存储和工作流实例
        WorkflowStore store = getPersistence();
        WorkflowEntry entry = store.findEntry(id);

        inputs.put(WORKFLOW_ID_KEY, Long.toString(entry.getId()));
        inputs.put(WORKFLOW_NAME_KEY, entry.getWorkflowName());

        // 如果工作流实例的状态不是已激活(ACTIVATED),则直接返回,不执行后续的动作。
        if (entry.getState() != WorkflowEntry.ACTIVATED) {
            return;
        }

        // 获取工作流描述和当前步骤
        WorkflowDescriptor wf = getConfiguration().getWorkflow(entry.getWorkflowName());

        List currentSteps = store.findCurrentSteps(id);
        ActionDescriptor action = null;

        PropertySet ps = store.getPropertySet(id);
        Map transientVars = new HashMap();

        if (inputs != null) {
            transientVars.putAll(inputs);
        }

        populateTransientMap(entry, transientVars, wf.getRegisters(), new Integer(actionId), currentSteps, ps);

        // 这部分代码首先检查全局动作,如果动作ID匹配并且动作可用,就将validAction设置为true
        boolean validAction = false;
        for (Iterator gIter = wf.getGlobalActions().iterator(); !validAction && gIter.hasNext(); ) {
            ActionDescriptor actionDesc = (ActionDescriptor) gIter.next();

            if (actionDesc.getId() == actionId) {
                action = actionDesc;

                if (isActionAvailable(action, transientVars, ps, 0)) {
                    validAction = true;
                }
            }
        }

        // 如果全局动作检查不通过,检查当前步骤的动作
        for (Iterator iter = currentSteps.iterator(); !validAction && iter.hasNext(); ) {
            Step step = (Step) iter.next();
            StepDescriptor s = wf.getStep(step.getStepId());

            for (Iterator iterator = s.getActions().iterator(); !validAction && iterator.hasNext(); ) {
                ActionDescriptor actionDesc = (ActionDescriptor) iterator.next();

                if (actionDesc.getId() == actionId) {
                    action = actionDesc;

                    if (isActionAvailable(action, transientVars, ps, s.getId())) {
                        validAction = true;
                    }
                }
            }
        }

        // 如果动作无效,抛出异常
        if (!validAction) {
            throw new InvalidActionException("Action " + actionId + " is invalid");
        }

        try {
            // 调用transitionWorkflow方法执行工作流的转换
            // 尝试执行工作流的转换,如果转换失败,检查是否可以隐式完成。如果在执行转换过程中发生异常,将上下文设置为只回滚,然后抛出异常
            if (!transitionWorkflow(entry, currentSteps, store, wf, action, transientVars, inputs, ps)) {
                checkImplicitFinish(action, id);
            }
        } catch (WorkflowException e) {
            context.setRollbackOnly();
            throw e;
        }
    }

# createNewCurrentStep方法

这个函数的主要目标是在工作流中创建一个新的当前步骤。以下是对这个函数的分段解释:

  1. 这个函数首先获取结果描述符中的下一个步骤。如果下一个步骤为-1,那么它将检查当前步骤是否存在。如果当前步骤存在,那么下一个步骤就是当前步骤的ID,否则,将抛出一个异常,因为请求的新的当前步骤与当前步骤相同,但是没有指定当前步骤。

    int nextStep = theResult.getStep();
    if (nextStep == -1) {
        if (currentStep != null) {
            nextStep = currentStep.getStepId();
        } else {
            throw new StoreException("Illegal argument: requested new current step same as current step, but current step not specified");
        }
    }
    
  2. 函数获取结果描述符中的所有者,并使用变量解析器将所有者中的变量翻译为实际的值。

    String owner = theResult.getOwner();
    VariableResolver variableResolver = getConfiguration().getVariableResolver();
    if (owner != null) {
        Object o = variableResolver.translateVariables(owner, transientVars, ps);
        owner = (o != null) ? o.toString() : null;
    }
    
  3. 函数获取结果描述符中的旧状态和新状态,并使用变量解析器将这些状态中的变量翻译为实际的值。

    String oldStatus = theResult.getOldStatus();
    oldStatus = variableResolver.translateVariables(oldStatus, transientVars, ps).toString();
    String status = theResult.getStatus();
    status = variableResolver.translateVariables(status, transientVars, ps).toString();
    
  4. 如果当前步骤存在,那么函数将标记当前步骤为已完成,并将当前步骤移动到历史记录中。

    if (currentStep != null) {
        store.markFinished(currentStep, actionId, new Date(), oldStatus, context.getCaller());
        store.moveToHistory(currentStep);
    }
    
  5. 函数创建开始日期和可能的截止日期。如果结果描述符中的截止日期不为空,那么函数将使用变量解析器将截止日期中的变量翻译为实际的值。

    Date startDate = new Date();
    Date dueDate = null;
    if ((theResult.getDueDate() != null) && (theResult.getDueDate().length() > 0)) {
        Object dueDateObject = variableResolver.translateVariables(theResult.getDueDate(), transientVars, ps);
        // ...省略了一些代码...
    }
    
  6. 函数在工作流存储中创建新的当前步骤,并将新步骤添加到transientVars映射中。

    Step newStep = store.createCurrentStep(entry.getId(), nextStep, owner, startDate, dueDate, status, previousIds);
    transientVars.put("createdStep", newStep);
    
  7. 如果previousIds参数不为null并且长度为0,同时当前步骤为null,那么函数将创建一个包含新步骤的列表,并将这个列表添加到transientVars映射中。

    if ((previousIds != null) && (previousIds.length == 0) && (currentStep == null)) {
        List currentSteps = new ArrayList();
        currentSteps.add(newStep);
        transientVars.put("currentSteps", new ArrayList(currentSteps));
    }
    
  8. 函数从transientVars映射中获取工作流描述符,并从描述符中获取下一个步骤的描述符。

    WorkflowDescriptor descriptor = (WorkflowDescriptor) transientVars.get("descriptor");
    StepDescriptor step = descriptor.getStep(nextStep);
    if (step == null) {
        throw new WorkflowException("step #" + nextStep + " does not exist");
    }
    
  9. 函数获取下一个步骤的描述符中的前置函数列表,并执行这些函数。

    List preFunctions = step.getPreFunctions();
    for (Iterator iterator = preFunctions.iterator(); iterator.hasNext(); ) {
        FunctionDescriptor function = (FunctionDescriptor) iterator.next();
        executeFunction(function, transientVars, ps);
    }
    
  10. 最后,函数返回新的当前步骤。如果在函数执行过程中发生了异常,那么函数将设置回滚标志,并抛出异常。

    try {
        // 省略.....
    		return newStep;
    } catch (WorkflowException e) {
        context.setRollbackOnly();
        throw e;
    }
    

下面是完整函数代码:

路径:https://github.com/ailohq/osworkflow/blob/master/src/main/java/com/opensymphony/workflow/AbstractWorkflow.java#L1449

private Step createNewCurrentStep(ResultDescriptor theResult, WorkflowEntry entry, WorkflowStore store, int actionId, Step currentStep, long[] previousIds, Map transientVars, PropertySet ps)
            throws WorkflowException {
        try {
            int nextStep = theResult.getStep();

            if (nextStep == -1) {
                if (currentStep != null) {
                    nextStep = currentStep.getStepId();
                } else {
                    throw new StoreException("Illegal argument: requested new current step same as current step, but current step not specified");
                }
            }

            if (log.isDebugEnabled()) {
                log.debug("Outcome: stepId=" + nextStep + ", status=" + theResult.getStatus() + ", owner=" + theResult.getOwner() + ", actionId=" + actionId + ", currentStep="
                        + ((currentStep != null) ? currentStep.getStepId() : 0));
            }

            if (previousIds == null) {
                previousIds = new long[0];
            }

            String owner = theResult.getOwner();

            VariableResolver variableResolver = getConfiguration().getVariableResolver();

            if (owner != null) {
                Object o = variableResolver.translateVariables(owner, transientVars, ps);
                owner = (o != null) ? o.toString() : null;
            }

            String oldStatus = theResult.getOldStatus();
            oldStatus = variableResolver.translateVariables(oldStatus, transientVars, ps).toString();

            String status = theResult.getStatus();
            status = variableResolver.translateVariables(status, transientVars, ps).toString();

            if (currentStep != null) {
                store.markFinished(currentStep, actionId, new Date(), oldStatus, context.getCaller());
                store.moveToHistory(currentStep);

                // store.moveToHistory(actionId, new Date(), currentStep,
                // oldStatus, context.getCaller());
            }

            // construct the start date and optional due date
            Date startDate = new Date();
            Date dueDate = null;

            if ((theResult.getDueDate() != null) && (theResult.getDueDate().length() > 0)) {
                Object dueDateObject = variableResolver.translateVariables(theResult.getDueDate(), transientVars, ps);

                if (dueDateObject instanceof Date) {
                    dueDate = (Date) dueDateObject;
                } else if (dueDateObject instanceof String) {
                    long offset = 0;

                    try {
                        offset = Long.parseLong((String) dueDateObject);
                    } catch (NumberFormatException e) {
                    }

                    if (offset > 0) {
                        dueDate = new Date(startDate.getTime() + offset);
                    }
                } else if (dueDateObject instanceof Number) {
                    Number num = (Number) dueDateObject;
                    long offset = num.longValue();

                    if (offset > 0) {
                        dueDate = new Date(startDate.getTime() + offset);
                    }
                }
            }

            Step newStep = store.createCurrentStep(entry.getId(), nextStep, owner, startDate, dueDate, status, previousIds);
            transientVars.put("createdStep", newStep);

            if ((previousIds != null) && (previousIds.length == 0) && (currentStep == null)) {
                // At this point, it must be a brand new workflow, so we'll
                // overwrite the empty currentSteps
                // with an array of just this current step
                List currentSteps = new ArrayList();
                currentSteps.add(newStep);
                transientVars.put("currentSteps", new ArrayList(currentSteps));
            }

            WorkflowDescriptor descriptor = (WorkflowDescriptor) transientVars.get("descriptor");
            StepDescriptor step = descriptor.getStep(nextStep);

            if (step == null) {
                throw new WorkflowException("step #" + nextStep + " does not exist");
            }

            List preFunctions = step.getPreFunctions();

            for (Iterator iterator = preFunctions.iterator(); iterator.hasNext(); ) {
                FunctionDescriptor function = (FunctionDescriptor) iterator.next();
                executeFunction(function, transientVars, ps);
            }

            return newStep;
        } catch (WorkflowException e) {
            context.setRollbackOnly();
            throw e;
        }
    }
}
最后更新: 9/12/2024, 11:20:51 PM